home *** CD-ROM | disk | FTP | other *** search
-
-
- .de PS
- .in +10
- ..
- .de PE
- .in -10
- ..
-
- .T tclMotif - a tutorial
-
- .H 1 Basics
-
- .H 2 Introduction
-
- tclMotif is a binding of the tcl language to the Motif widget set.
- This allows access to most of the features of Motif through the
- simple language tcl, instead of the more complex C language that Motif is
- written in.
-
- There are major gains in this, primarily in code size and complexity: at
- tclMotif program typically is one tenth the length of the corresponding C
- program. It also dramatically simplifies some of the more complex areas of
- Motif programming, particularly in the handling of strings: tclMotif looks
- after the C code that otherwise the programmer would have to deal with.
-
- There is a downside, but it is comparatively minor. tcl is an interpreted
- language with a single data-type: string. There is a speed penalty in
- converting to and from strings. This would bar tcl from, say, a realistic
- language for matrix manipulations. However, sitting above a large and
- complex widget toolkit such as Motif, this speed penalty is not noticeable.
- tcl does not have the structuring and hiding capabilities of C, nor the
- object language mechanisms of C++. This may cause problems in very large
- projects, though tcl programs of over 100,000 lines in length (probably
- equivalent to 1,000,000 lines of C) do exist.
-
- .H 2 "Starting moat
-
- tclMotif is usually accessed through an interpreter called ``moat''. This
- is an interpreter that can either run interactively or on a tclMotif file.
- If the command is typed with no arguments then it displays the prompt
- .PS
- %
- .PE
- This acts just like the ordinary tcl interpreter ``tclsh'', and tcl
- commands can be typed into this interpreter just like into tclsh. The only
- difference is that it will also accept the additional Motif commands
- supplied by tclMotif.
-
- When run with a single argument that is the name of a file as in
- .PS
- moat example1
- .PE
- it will read tclMotif commands from the file (here, ``example1'') and
- execute them.
-
- There is a third way of running tclMotif, and this is the most common use.
- It relies on a number of things to make it work, though. First, the
- ``moat'' program must be installed in a standard place. Most tcl-based
- interpreters such as tclsh and moat assume that this is in the directory
- /usr/local/bin. If neccessary, you or your system supervisor should ensure
- that moat is either in this directory or that a link is made from this
- directory to it. Secondly, yous must be running a suitable command shell.
- All of the common shells such as sh, ksh, csh, bash, ksh, tcsh, zsh are
- fine. With these given, you can run a file as a tclMotif progam program by
- .AL 1
- .LI
- Making the first line of the file
- .PS
- #!/usr/local/bin/moat
- .PE
- .LI
- Making the file executable by
- .PS
- chmod u+x <file>
- .PE
- .LE
- From then on, using the filename as a command will invoke the moat
- interpreter to read and execute the rest of the file.
-
- .H 2 "Hello World
-
- Motif programs run under an event driven model. That is, objects are
- created that respond to events such as button presses, key strokes, and
- other things such as being covered and uncovered by other windows. To
- manage this, Motif relies on a toolkit called Xt that allows objects to be
- created and handles the events to the obects. Any Xt-based system must
- start up the Xt world, create some objects and then hand control to an Xt
- event handler that sits in a loop reading and processing events.
-
- In tclMotif, the Xt world is started by a call to ``xtAppInitialize''. This
- creates a special object ``.'' which is used to build all other objects
- from. Two special calls are made to this object: ``realiseWidget'' that
- creates windows for all objects, and ``mainLoop'' that enters the event
- processing loop.
-
- The actual objects are supplied by Motif. For ``Hello World'' a very
- simple object can be used because it just has to display a piece of text.
- This object is a Label. It is created by a call ``xmLabel'', and has its
- labelString value set to ``Hello world''
- .PS
- xtAppInitialize
- xmLabel .label -labelString "Hello world"
- . realizeWidget
- . mainLoop
- .PE
-
- All widgets are ultimately descended from the toplevel widget ``.''. The
- hierarchy is shown explicitly in the widget names, so ``.label'' is an
- immediate child of ``.''. This is similar to the path name for files in
- Unix, with the ``.'' taking the place of the Unix file separator ``/''.
- The third and fourth lines are method calls for the ``.'' object to
- create windows and enter the event loop.
-
- .H 2 "Creating widgets
-
- Motif has a large number of widgets. In C, there is a function to create
- each one of them as XmCreate<widget>. tclMotif simplifies this a little to
- xm<widget>. So in the example above ``xmLabel'' creates a Label widget.
- Similarly, ``xmText'' creates a Text widget, ``xmList'' creates a List
- widget and so on. A program that creates a PushButton widget showing
- ``Hello world'' is
- .PS
- xtAppInitialize
- xmPushButton .label -labelString "Hello world"
- . realizeWidget
- . mainLoop
- .PE
- where only the widget creation command has changed.
-
- Each widget has a parent, so that widgets form a tree. The toplevel widget
- is ``.'' and forms the root of this tree. The toplevel widget is created by
- the command ``xtAppInitialize''. A widget is known by name, which is
- specified as the first argument to the widget creation command. This name
- contains the full path from ``.''. In the examples shown, this path is very
- short, as each widget is just the child of ``.'' It is worth noting at this
- point that the toplevel widget can only have one widget child at a time
- (except for dialog widgets which are discussed later).
-
- In general, an application will consist of a non-trivial tree of widgets.
- Some widgets are \fI container \fP widgets, whose purpose is to contain and
- manage their children. The major containers are RowColumn, Form and
- MainWindow.
-
- In most instances, a MainWindow widget will be the immediate child of ``.'',
- as this is set up to support a menu bar and a principal window containing
- the application, with optionally some scrollbars to control the visible
- area of the application.
-
- A Form is used for arbitrary geometry arrangements, whereas a RowColumn is
- used for regular arrangements. In this example, a RowColumn inside a
- MainWindow holds a set of Labels:
- .PS
- xmMainWindow .main
- xmRowColumn .main.rc
- xmLabel .main.rc.label1
- xmLabel .main.rc.label2
- xmLabel .main.rc.label3
- .PE
-
- A widget when created will not show on the screen. Windows have to be
- created, which is done by ``. realizeWidget''. In addition, a container
- will not show a widget unless it has been placed under the geometry
- management of the parent. This is done either at creation time with an
- optional second parameter ``managed'' or by a special method
- ``manageChild'':
- .PS
- xmLabel .main.rc.label1 managed
- xmLabel .main.rc.label2
- .main.rc.label2 manageChild
- .PE
-
- .H 2 "Resource control
-
- A widget has many configurable properties. For example, in a Label widget
- the label itself can be set; in addition, the font used to render the label
- is configurable, as well as whether it should be centred or justified to
- the right or left. Width, height, foreground and background colour are
- configurable properties for most widgets. Each of these properties is
- called a \fI resource \fP, and the value is called a \fI resource value\fP.
-
- The set of resources and possible values is documented for each widget.
- For example, the ArrowButton widget has a resource ``arrowDirection'' with four
- possible values: arrow_up, arrow_down, arrow_left and arrow_right. Resources
- can be set using the method ``setValues'' for each widget, or at creation
- time.
-
- To set resources at creation time, a list of resource names/resource value
- pairs follows the widget name. For example,
- .PS
- xmArrowButton .arrow managed \
- -arrowDirection arrow_up \
- -width 100 \
- -height 100
- .PE
-
- To set resources at any time, the set of name/value pairs follows the
- method name, as in
- .PS
- .arrow setValues \
- -arrowDirection arrow_down \
- -width 50
- .PE
-
- In a similar manner, resource values can be obtained from a widget by using
- the method getValues. This also takes a list of pairs, this time the
- resource name and a tcl variable to store the value in. For example, to
- find the width and height of a widget and store them in variables w and h
- respectively,
- .PS
- .arrow getValues \
- -height h \
- -width w
- .PE
-
- .H 2 "Callback functions
-
- When a button is pushed, when a list item is selected, when a key is
- pressed using a Text widget, etc, the widget reacts in some way. For
- example, the pressed button appears to sink into the surface, the list item
- is highlighted, and the character pressed is entered into the Text widget.
- This is also the time that suitable user code is executed. When the widget
- executes one of the internal actions that changes its state it can call tcl
- code that has been registered with the widget. Such code is attached to a
- ``callback'' and is called ``callback code''. Each widget has a documented
- set of callbacks. For example, for PushButton there is the
- activateCallback. activateCallback code is executed whenever the button is
- pressed and released. In a similar manner, when the List is in single-select mode,
- whenever an item is selected code attached to the singleSelectionCallback
- is executed.
-
- Code is attached to the callback by using the appropriate callback method.
- There is a callback method for each of a widget's callbacks, and the code
- is given as the argument to this method. For example, to say ``Ouch''
- whnever the button is pressed and released,
- .PS
- .btn activateCallback {puts stdout "Ouch"}
- .PE
-
- Any tcl code may be placed in the callback. For example, a call to a
- function may be made, or a series of calls may be made. For example,
- .PS
- .btn activateCallback {
- puts stdout "That"
- puts stdout "hurt!"
- }
- .PE
-
- Callback code is executed in the global context, so that any unevaluated
- variables are evaluated at runtime in the global environment. That is,
- in
- .PS
- .btn activateCallback {puts stdout $msg}
- .PE
- the braces {} protect the variable msg against expansion when the code is
- attached to the callback. When the code is actually executed the variable
- msg will be evaluated as a global variable.
-
- When a callback is invoked, there is often a great deal of useful
- information that is available to the callback code. The simplest and most
- commonly used of these is the widget name. This and other information can
- be found by means of a pattern which always starts with ``%''. For example,
- the pattern for the widget name is ``%w'':
- .PS
- .btn activateCallback \
- {puts stdout "widget %w was pushed"}
- .PE
-
- .H Rowcolumn
-
- .H Form
-
- .H 2 Menus
-
- .H 2 "Dialog widgets
-
- .H 2 List
-
- .H 2 Text
-
- .H 2 "Extended example
-
- xmeditor is an editor based on the Text widget. It gains all of its edit
- functionality from the Text widget, so that this program is basically just
- a wrapper around this widget. Nevertheless, it shows a large number of the
- features of tclMotif. This example is a modification of the
- xmeditor example that appears as an example in the Motif distribution.
-
- .H 3 "Xt world
- There is a section to setup the basic geometry and start the Xt world
- going:
- .PS
- xtAppInitialize \
- -fallbackResources {
- {*XmText.columns: 80}
- {*XmText.rows: 24}
- }
-
- xmMainWindow .main managed
-
- # menu stuff to create .main.menuBar goes in here
- # ....
-
- xmScrolledText .main.text managed \
- -editMode multi_line_edit
- .main.text modifyVerifyCallback {set fileSaved 0}
-
-
- .main setValues -menuBar .main.menuBar \
- -workWindow [.main.text parent]
-
- . realizeWidget
- . mainLoop
- .PE
-
- The fallback resources set the default sizes of the Text widget. These can be
- overridden by user or app-defaults files.
- The application resides inside a MainWindow, which contains a menu bar and a
- ScrolledText widget. The relation of the children within the MainWindow by
- the line
- .PS
- .main setValues -menuBar .main.menuBar \
- -workWindow [.main.text parent]
- .PE
- which sets the menuBar and workWindow resources. Note that the
- .I parent
- of the Text widget has to be used. This is the only way to get at the
- ScrolledWindow that is the real child of the MainWindow.
-
- The Text widget by default is a single-line editor. This is clearly
- inappropriate here and is not even something that you would want to
- leave to user resource files. So the editMode resource is hard-coded
- to have the value ``multi_line_edit''.
- There is a callback set for the Text widget: whenever any text is entered
- into the widget, changing its contents, the global variable fileSaved is
- set to false, so that the application can determine that the file on disk
- is not the same as in the widget.
-
- Finally, all windows are created by calling ``realizeWidget'' on the
- widget ``.'', and mainLoop is entered on the same widget.
-
- .H 3 "Menu system
-
- The menu system consists of three toplevel entries: File, Edit and Help.
- Under File is the pulldown with New, Open, Save, Save As..., Quit. Under
- Edit is the pulldown with entries Cut, Copy, Paste and Clear. Help has only
- one entry, About.
-
- The subset of this that defines the menubar and the file pulldown is
- .PS
- xmMenuBar .main.menuBar managed
-
- # file pulldown
- xmPulldownMenu .main.filePane
- xmCascadeButton .main.menuBar.File managed \
- -subMenuId .main.filePane
-
- xmPushButton .main.filePane.New managed
- .main.filePane.New activateCallback newFileCB
-
- xmPushButton .main.filePane.Open managed
- .main.filePane.Open activateCallback openFileCB
-
- xmPushButton .main.filePane.Save managed
- .main.filePane.Save activateCallback "saveFile"
-
- xmPushButton .main.filePane.SaveAs managed
- .main.filePane.SaveAs activateCallback "saveFileAsCB"
-
- xmPushButton .main.filePane.Quit managed
- .main.filePane.Quit activateCallback quitFileCB
- .PE
-
- A small comment is in order: the buttons pick up their labelString value by
- default from their name, which is File, New, etc. This gives the default as
- English names. If other names are desired, they may be set using resources
- files.
-
- Each of these buttons calls a callback function. These functions will be
- discussed later.
-
- .H 3 "File state
-
- The contents of the editor may be the same as the external file. This
- will occur after a Save and before any changes have occurred, after
- the file has been loaded, or when a new file has been created. In this
- case, many actions such as exiting the editor or loading a new file
- can be done without any problems. On the other hand, the two may have
- different contents. In this case many actions may need to call up a
- dialog of some kind. In xmeditor this state is represented by the tcl
- variable ``fileSaved''. In various places the value of this is set to
- 1 (true). It is set to 0 (false) every time a change is made to the
- widget contents through an edit action. This is handled very easily in
- the ``modifyVerifyCallback'' which is called on every text entry by the
- user:
- .PS
- .main.text modifyVerifyCallback {set fileSaved 0}
- .PE
-
- .H 3 "Saving and loading
-
- Two simple procedures are just concerned with saving the text from the
- Text widget and loading it from a file. These are ``saveFile'' and
- ``loadFile''. The Motif part of saveFile of interest is extracting
- the contents from the Text widget:
- .PS
- set str [.main.text getString]
- .PE
- Similarly, from loadFile, lines are read fromthe file and placed in the
- Text widget by
- .PS
- .main.text setString ""
- while {[gets $fileID str] >= 0} {
- .main.text insert [.main.text getLastPosition]] "$str\n"
- }
- .PE
- This reads lines successively and inserts them at the end of the text in
- the widget.
-
- .H 3 "Save dialog
-
- One of the more complex functions is ``saveFileDialog''. This function
- is called when an action such as repalcing the contents of the Text
- widget with new text has been requested, but the current contents have
- not been saved. The proper action in this case is to request confirmation
- from the user to save or discard the contents. Usually such a confirmation
- is run as a
- .I modal
- dialog
- i.e. it must be answered before any other interaction with the application
- is allowed.
-
- Creating the dialog is straightforward, as it is just an errorDialog with
- messageString set to a ``Save file?'' message. making it modal is done
- in several stages. First the dialog is made modal by setting the
- ``dialogStyle'' resource to dialog_full_application_modal.
- This in itself is enough to ensure that no
- interaction with the rest of the application can take place.
- .PS
- xmErrorDialog .fileSaveDialog managed \
- -messageString "Save $currentFile?" \
- -dialogStyle dialog_full_application_modal
- .PE
-
- While this sets the interaction style with the application, it does not set
- the control flow in the program. The use of such modal dialogs is often
- in linear code:
- .PS
- if modal_dialog returns true
- do something
- else
- do something else
- .PE
- To ensure this kind of logic, the dialog function itself cannot return
- until the dialog is over. The simplest way of ensuring this is to enter
- an event loop within the dialog function, which only terminates when the
- dialog is over. This in turn will only happen when the user presses either
- the Cancel or OK buttons. So an event loop must be set up which only
- terminates when the Cancel or OK callbacks are invoked:
- .PS
- global stillModal
- global answer
-
- .fileSaveDialog okCallback {set stillModal 0; set answer 1}
- .fileSaveDialog cancelCallback {set stillModal 0; set answer 0}
-
- set stillModal 1
- while {$stillModal} {
- . processEvent
- }
- .PE
-
- .H 3 "File selection
-
- At various points, a file must be chosen. This should be done by a
- FileSelection dialog. This selects a file and loads it:
- .PS
- xmFileSelectionDialog .fsd managed
- .fsd.Help unmanageChild
- .fsd okCallback {loadFile %w %value}
- .fsd cancelCallback {[.fsd parent] destroyWidget}
- .PE
-